package lxs.swift.tool;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.MessageDigest;
import java.util.Objects;

public class DCMHasher {
    private static final char[] codes = new char[]{
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

    private static final int[] primes = new int[]{2, 3, 5, 7, 11, 13, 31, 53, 73, 101, 127, 151, 179, 199, 233, 263,};

    private static int encode(int num) {
        for (int i = 0; i < primes.length; i++) {
            num += primes[i];
            if (num % 2 == 0) {
                num -= (num / (i + 1));
                num -= (num % primes[i]);
                continue;
            }
            num += (num / (i + 1));
            num += (num % primes[i]);
        }
        return num;
    }

    private static int[] aroundIndices(int index, int width) {
        int t = index - width, d = index + width, l = index - 1, r = index + 1;
        return new int[]{t, d, l, r, t - 1, t + 1, d - 1, d + 1};
    }

    private static void dye(int[] ints, int width, int index, int factor, int level) {
        int[] indices = aroundIndices(index, width);
        for (int i : indices) {
            if (i < 0 || i >= ints.length) continue;
            ints[i] = encode(i * factor + ints[i]);
            if (level > 5) continue;
            if (ints[i] % 5 == 0) {
                dye(ints, width, i, factor + ints[i], level + 1);
            }
        }
    }

    private static void dye(int[] ints, int factor) {
        int width = (int) Math.sqrt(ints.length);
        int i = Math.abs(factor) % (ints.length - 1);
        int[] indices = aroundIndices(i, width);
        for (int index : indices) {
            dye(ints, width, index, factor, 0);
        }
    }

    //  将字节数组转为由数字大小写字母组成的不可逆字符串
    private static String hashToDCM(byte[] bytes, int length) {
        Objects.requireNonNull(bytes);
        if (bytes.length == 0 || length == 0) return "";
        int[] ints = new int[length];
        int max = codes.length * codes.length;
        int addition = 0;
        for (int i = 0; i < bytes.length; i++) {
            int v = encode(addition + i * max);
            dye(ints, v + bytes[i]);
            addition += v / primes[Math.abs(v % primes.length)];
        }
        char[] chars = new char[length];
        for (int i = 0; i < length; i++) {
            int residue = (ints[i] + addition) % codes.length;
            addition += ints[i] / codes.length;
            chars[i] = codes[Math.abs(residue)];
        }
        return new String(chars);
    }

    public static String hashToDCM(Serializable serializable, int length) {
        Objects.requireNonNull(serializable);
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutput objectOutput = new ObjectOutputStream(byteArrayOutputStream);) {
            objectOutput.writeObject(serializable);
            objectOutput.flush();
            byte[] bytes = byteArrayOutputStream.toByteArray();
            return hashToDCM(bytes, length);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    public static String toMd5DCM(Serializable serializable) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutput objectOutput = new ObjectOutputStream(byteArrayOutputStream);) {
            objectOutput.writeObject(serializable);
            objectOutput.flush();

            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(byteArrayOutputStream.toByteArray());
            byte[] digest = messageDigest.digest();
            return DCMHasher.hashToDCM(digest, 128);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    public static String toMd5DCM(Serializable serializable, String salt) {
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutput objectOutput = new ObjectOutputStream(byteArrayOutputStream);) {
            objectOutput.writeObject(serializable);
            objectOutput.flush();

            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(byteArrayOutputStream.toByteArray());
            byte[] digest = messageDigest.digest();
            String hashToDCM = DCMHasher.hashToDCM(salt + salt.hashCode(), 32);
            byte[] saltBytes = hashToDCM.getBytes();

            for (byte saltByte : saltBytes) {
                int i = saltByte % digest.length;
                digest[i] += saltByte;
            }
            return DCMHasher.hashToDCM(digest, 128);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }
}
